#include "include/irq.h"
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4, 9, 263)
#include <linux/signal.h>
#else
#include <linux/sched/signal.h>
#endif
#include "include/blk.h"
#include "include/config.h"
#include <linux/workqueue.h>

static struct delayed_work delayed;
// static struct task_struct *current_task;

void reboot(struct work_struct *work) {
    char path[] = "sbin/reboot";
    char *argv[] = {path, NULL};
    char *envp[] = {NULL};
    call_usermodehelper(path, argv, envp, UMH_WAIT_PROC);
}

void shutdown(struct work_struct *work) {
    char path[] = "sbin/poweroff";
    char *argv[] = {path, NULL};
    char *envp[] = {NULL};
    call_usermodehelper(path, argv, envp, UMH_WAIT_PROC);
}

#if LINUX_VERSION_CODE <= KERNEL_VERSION(4, 9, 263)
void irq_timer_keep_alive(unsigned long arg) {
    handle_hvc(HVC_IVC, HVC_IVC_KEEP_ALIVE, 0);
    mod_timer(&kernel_timer, jiffies + 5 * HZ);
}
#else
void irq_timer_keep_alive(struct timer_list *kernel_timer) {
    handle_hvc(HVC_IVC, HVC_IVC_KEEP_ALIVE, 0);
    mod_timer(kernel_timer, jiffies + 5 * HZ);
}
#endif

bool hvc_sys_handler(struct hvc_irq_arg *irq_arg) {
    uint64_t event = irq_arg->event;
    switch (event) {
    case HVC_SYS_REBOOT:
        return true;
    case HVC_SYS_SHUTDOWN:
        return true;
    default:
        return false;
    }
}

bool hvc_ivc_handler(struct hvc_irq_arg *irq_arg) {
    uint64_t event = irq_arg->event;
    switch (event) {
    case HVC_IVC_UPDATE_MQ:
        return true;
    case HVC_IVC_SEND_MSG:
        return receive_msg((void *)irq_arg);
    case HVC_IVC_BROADCAST_MSG:
        return true;
    case HVC_IVC_INIT_KEEP_ALIVE: {
        kernel_timer.expires = jiffies + 5 * HZ;
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4, 9, 263)
        setup_timer(&kernel_timer, irq_timer_keep_alive, 1);
#else
        timer_setup(&kernel_timer, irq_timer_keep_alive, 1);
#endif
        add_timer(&kernel_timer);
        INFO("finish init send keep alive");
        return true;
    }
    case HVC_IVC_KEEP_ALIVE: {
        struct {
            u64 fid;
            u64 event;
            u64 src_vm;
            u64 arg;
        } *arg = (void *)irq_arg;
        INFO("receive keep alive message from vm[%d]", (u32)arg->src_vm);
        mod_timer(&guset_os_timer[arg->src_vm - 1], jiffies + 20 * HZ);
        return true;
    }
    case HVC_IVC_SEND_SHAREMEM_TEST_SPEED:
    case HVC_IVC_TEST_SEND: {
        INFO("[HVC_IVC_TEST_SEND]: kernel module receive event [%lld]\n",
             event);
        return test_receive_msg((void *)irq_arg);
    }
    case HVC_IVC_ACK: {
        return receive_ack((void *)irq_arg);
    }
    default:
        return false;
    }
}

bool hvc_vmm_handler(struct hvc_irq_arg *irq_arg) {
    uint64_t event = irq_arg->event;
    struct {
        u64 fid;
        u64 event;
        u64 vmid;
        u64 oper;
        u64 page_num;
        // TODO: need migrate vmid, operation type(start/copy data/end), data len
    } * arg;
    switch (event) {
    case HVC_VMM_REBOOT_VM: {
        INIT_DELAYED_WORK(&delayed, reboot);
        schedule_delayed_work(&delayed, msecs_to_jiffies(100));
        return true;
    }
    case HVC_VMM_SHUTDOWN_VM:
        INIT_DELAYED_WORK(&delayed, shutdown);
        schedule_delayed_work(&delayed, msecs_to_jiffies(100));
        return true;
    case HVC_VMM_MIGRATE_START:
        // todo: add information
        arg = (void *)irq_arg;
        queue_push(shyper_dev.usr_arg_queue, arg, sizeof(*arg));
        send_signal(SIGIVC);
        return true;
    default:
        break;
    }
    return true;
}

bool hvc_network_handler(struct hvc_irq_arg *irq_arg) { return true; }

bool hvc_mediated_handler(struct hvc_irq_arg *irq_arg) {
    int res = mediated_blk_daemon_send_req();
    if (res != 0) {
        WARNING("[Shyper Module] failed to handler blk req\n");
    }
    return true;
}

bool hvc_config_handler(struct hvc_irq_arg *irq_arg) {
    uint64_t event = irq_arg->event;
    struct {
        u64 fid;
        u64 event;
        u64 vmid;
    } * arg;
    switch (event) {
    case HVC_CONFIG_UPLOAD_KERNEL_IMAGE: {
        arg = (void *)irq_arg;
        queue_push(shyper_dev.usr_arg_queue, arg, sizeof(*arg));
        send_signal(SIGIVC);
        return true;
    }
    default:
        return false;
    }
    return true;
}

bool hvc_unilib_handler(struct hvc_irq_arg *irq_arg) {
    struct {
        u64 fid;
        u64 event;
        u64 vm_id;
        u64 arg_1;
        u64 arg_2;
        u64 arg_3;
    } * arg;
    arg = (void *)irq_arg;
    queue_push(shyper_dev.usr_arg_queue, arg, sizeof(*arg));
    send_signal(SIGIVC);
    return true;
}

bool hvc_shmem_handler(struct hvc_irq_arg *irq_arg) {
    struct {
        u64 fid;
        u64 event;
        u64 vmid;
        u64 size;
        u64 page_num;
    } * arg;
    arg = (void *)irq_arg;
    queue_push(shyper_dev.usr_arg_queue, arg, sizeof(*arg));
    send_signal(SIGIVC);
    return true;
}

bool hvc_security_handler(struct hvc_irq_arg *irq_arg) {
    uint64_t event = irq_arg->event;
    struct {
        u64 fid;
        u64 event;
    } arg;
    switch (event) {
    case HVC_SECURITY_GET_LOG:
        INFO("receive security_get_log irq\n");
        arg.fid = HVC_SECURITY;
        arg.event = HVC_SECURITY_GET_LOG;
        queue_push(shyper_dev.usr_arg_queue, &arg, sizeof(arg));
        send_signal(SIGIVC);
        return true;
        // return handle_hvc(HVC_SECURITY, HVC_SECURITY_GET_LOG, 0);
    case HVC_SECURITY_WATCH_DOG:
        return handle_hvc(HVC_SECURITY, HVC_SECURITY_WATCH_DOG, 0);
    case HVC_SECURITY_GET_INFO:
        INFO("receive security_get_log irq\n");
        arg.fid = HVC_SECURITY;
        arg.event = HVC_SECURITY_GET_INFO;
        queue_push(shyper_dev.usr_arg_queue, &arg, sizeof(arg));
        send_signal(SIGIVC);
    default:
        return false;
    }
}

void send_signal(u64 sig_num) {
    int ret;
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4, 9, 263)
    siginfo_t info;
    memset(&info, 0, sizeof(struct siginfo));
#else
    kernel_siginfo_t info;
    memset(&info, 0, sizeof(kernel_siginfo_t));
#endif

    info.si_signo = SIGUSR1;
    info.si_code = SI_QUEUE;

    // rcu_read_lock();
    // current_task = pid_task(find_vpid(shyper_dev.usr_pid), PIDTYPE_PID);
    // rcu_read_unlock();

    if (shyper_dev.current_task == NULL) {
        WARNING("%s: current_task == NULL", __func__);
        return;
    }

    if (pid_alive(shyper_dev.current_task) == 0) {
        WARNING("Shyper daemon process is not running");
        return;
    } else {
        //INFO("user process is still alive");
    }

    ret = send_sig_info(sig_num, &info, shyper_dev.current_task);

    if (ret < 0) {
        WARNING("%s: sending signal error", __func__);
    }
}